需求 同事写了一个简单的phonePage来进行telephone的自动化测试,并且在通话时页面上会添加一个元素,通过srcObject属性能够拿到本地以及对短的audio stream,需求是在开发的firefox extension上利用这个stream来录制语音。
如何在extension获取页面上的元素 在extension的manifest.json里,有一个content_scripts配置,其中 js关键字可以实现将js代码注入到页面中,看下官方对js关键字的解释:
An array of paths, relative to the manifest.json file, referencing JavaScript files that will be injected into matching pages. Files are injected in the order given. This means that, for example, if you include jQuery here followed by another content script, like this: “js”: [“jquery.js”, “my-content-script.js”] then “my-content-script.js” can use jQuery. Files are injected at the time specified by run_at .
何时开始/停止录制 在puppeteer端给extension发送消息:
1 browser.runtime.sendMessage(extensionID, {messageObj})
在background.js里进行事件监听:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 browser.runtime.onMessageExternal.addListener(request => { const runID = request.runID; const recordID = request.recordID; console.log("extensionID", extensionID); console.log("From console or puppeteer message request:", request); console.log("extension recv message, type:", request.message); if (request.message === 'start') { // browser.tabs.sendMessage(extensionID, {method: "start"}).then(res=> { browser.tabs.sendMessage(2, {method: "start"}).then(response=> { console.log("From content:", response.message); }).catch(err=>{ console.log("Error from content:", err); }) }else if(request.message === 'stop') { // browser.tabs.sendMessage(extensionID, {method: "stop"}).then(res=> { browser.tabs.sendMessage(2, {method: "stop"}).then(res=> { console.log('background recv chunks:', res.audioUrl); let format = 'audio/ogg;codecs=opus'; let blob = new Blob(res.audioUrl, { type: format }); let url = window.URL.createObjectURL(blob); console.log('generate audio .ogg url:', url) const currentDate = Date.now(); chrome.downloads.download({ url: url, filename: `browserless/audio-file.${runID}.${recordID}.${currentDate}.ogg`, saveAs: false }) }).catch(err=>{ console.log('Error from content:', err) }) } });
在content.js里进行录制声音 因为前面说到content.js的内容会注入到页面中,所以可以在content.js里录制声音。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 var mediaRecorder;const chunks = [];var interval;function fun (request, sender, sendResponse ) { if (request.method == "start" ){ var videoElement = document .getElementById('rc_audio_div' ).lastElementChild; mediaRecorder = new MediaRecorder(videoElement.srcObject); mediaRecorder.ondataavailable = function (evt ) { chunks.push(evt.data); }; mediaRecorder.start(); sendResponse({message : 'The content side starts recording' }); } else if (request.method == "stop" ) { console .log("mediaRecorder begin to stop" ); mediaRecorder.stop(); new Promise (resolve => { interval = setInterval(() => { if (chunks.length != 0 ){ console .log("to send chunks:" ,chunks); sendResponse({audioUrl : chunks}); clearInterval(interval); resolve(); } }, 1000 ); }); } return true ; } console .log(chrome.runtime.onMessage.hasListener(fun));browser.runtime.onMessage.addListener(fun); console .log(chrome.runtime.onMessage.hasListener(fun));
注意: 要在backgroud里生成blob,因为在content中生成的时候,background会因为权限问题拿不到前端页面生成的音频,所以我只在content中录制,并把数据chunks返回给后端,在后端生成并下载。 这里还有一个坑就是直接返回chunks会是空数组,因为chrome.runtime.onMessage
默认是同步的,不会等chunks填充数组就返回,所以要在listener function
最后返回true,详情可阅读 runtime.onMessage
.
References